// SPDX-License-Identifier: GPL-2.0
/*
 * MCA features for Intel X86.
 * Copyright (C) 2017-2024 Huawei Corporation
 */
#include <linux/notifier.h>
#include <asm/mce.h>
#include <linux/uaccess.h>

#include "internal.h"
#include "euleros_mce_custom_handler.h"

/*
 * Count for memory errors that will trigger panic.
 * The counter will NOT be reset, due to below scenarios:
 * 1. timeout during execution Monarch's reign.
 * 2. mce panic reentry.
 * Anyway system is going to RESTART or RESET.
 */
static atomic_t mce_mem_ctl_err_cnt;

enum rules_type_idx {
	IDX_NOT_PANIC = 0,
	IDX_PANIC_NOTIFY = 1,
	IDX_MEM_ISOLATE = 2,
	MAX_NUM_OF_RULES_TYPE
};

typedef int (*mce_match_fn_t)(struct mce *m, struct mce *r);

#define MAX_NUM_OF_RULES	(3)
struct mce_rules {
	const char *name;
	mce_match_fn_t match_fn;
	int num;
	struct mce rule[MAX_NUM_OF_RULES];
};

static int mce_not_panic_rules(struct mce *m, struct mce *r);
static int mce_panic_notify_rules(struct mce *m, struct mce *r);
static int mce_mem_isolate_rules(struct mce *m, struct mce *r);

static struct mce_rules rules[MAX_NUM_OF_RULES_TYPE] = {
	{
		.name = "not_panic",
		.match_fn = mce_not_panic_rules,
	},
	{
		.name = "panic_notify",
		.match_fn = mce_panic_notify_rules,
	},
	{
		.name = "mem_isolate",
		.match_fn = mce_mem_isolate_rules,
	}
};

#define MCE_BIT_MATCH(mce, rule)	(((mce) & (rule)) == (rule))

/* show rules configured currently. */
static void mce_show_rules(void)
{
	int i, t;
	int rule_num;
	struct mce_rules *rule;

	for (t = 0; t < MAX_NUM_OF_RULES_TYPE; t++) {
		rule = &rules[t];
		rule_num = rule->num;
		for (i = 0; i < rule_num && i < MAX_NUM_OF_RULES; i++)
			pr_notice("%s (status: 0x%llx)\n", rule->name, rule->rule[i].status);
	}
}

static void mce_add_rule(struct mce_rules *rule, const struct mce * const r)
{
	const int idx = rule->num;

	if (idx < MAX_NUM_OF_RULES) {
		memcpy(&rule->rule[idx], r, sizeof(struct mce));
		rule->num++;
	} else {
		pr_notice("%s reach max num (%d)\n", rule->name, idx);
	}
}

/*
 * The conditions of mce not panic.
 *
 * @m: mce of BANK MSR
 * @r: mce rlues
 * return value: 1 -match, 0 -mismatch
 */
static int mce_not_panic_rules(struct mce *m, struct mce *r)
{
	/* For simplification judgment, no CPU family/model/bank range check */
	return MCE_BIT_MATCH(m->status, r->status);
}

/*
 * The condition of mce panic notifier is the severity should be panic.
 *
 * @m: mce of BANK MSR
 * @r: mce rlues
 * return value: 1 -match, 0 -mismatch
 */
static int mce_panic_notify_rules(struct mce *m, struct mce *r)
{
	if (m->severity >= MCE_PANIC_SEVERITY) {
		if (MCE_BIT_MATCH(m->status, r->status))
			return 1;
	}
	return 0;
}

/*
 * The conditions of mce memory isolate.
 *
 * @m: mce of BANK MSR
 * @r: mce rlues
 * return value: 1 -match, 0 -mismatch
 */
static int mce_mem_isolate_rules(struct mce *m, struct mce *r)
{
	return MCE_BIT_MATCH(m->status, r->status);
}

/*
 * Check if the mce matches the rules customer configured.
 * @m: mce of BANK MSR
 * @r: mce rlues
 * return value: 1 -match, 0 -mismatch
 */
static int mce_match_rules(struct mce *m, struct mce_rules *r)
{
	int i = 0;
	int rules_num = 0;

	rules_num = r->num;
	for (i = 0; i < rules_num && i < MAX_NUM_OF_RULES; i++) {
		if (r->match_fn(m, &(r->rule[i])))
			return 1;
	}

	return 0;
}

/* Check if mce matches the "NOT panic" rules. */
int mce_check_not_panic(struct mce *m)
{
	return mce_match_rules(m, &rules[IDX_NOT_PANIC]);
}

/* if mce that lead to panic, increase the counter. */
void mce_count_mem_err(struct mce *m)
{
	if (mce_match_rules(m, &rules[IDX_PANIC_NOTIFY])) {
		atomic_inc(&mce_mem_ctl_err_cnt);
		pr_notice("cpu(%d) %s status: 0x%llx(severity: %u). memory error(%d) increased\n",
					smp_processor_id(), __func__, m->status, m->severity,
					atomic_read(&mce_mem_ctl_err_cnt));
	}
}

int mce_check_mem_err(void)
{
	return atomic_read(&mce_mem_ctl_err_cnt);
}

/* Check if mce info matches the memory isolation rules. */
int mce_mem_isolate(struct mce *m)
{
	return mce_match_rules(m, &rules[IDX_MEM_ISOLATE]);
}

#define MAX_LEN_SINGLE_RULE	(strlen("0xFFFFFFFFFFFFFFFF"))
#define MIN_LEN_SINGLE_RULE	(strlen("0x0"))

static void mce_parse_rules(char *options, const enum rules_type_idx i)
{
	char rule_buf[32];
	char * const delim = ",";
	char *token;
	int rule_buf_len = 0;
	u64 rule;
	struct mce m;
	struct mce_rules *r = &rules[i];

	memset(&m, 0x0, sizeof(m));

	while (NULL != (token = strsep(&options, delim))) {
		rule_buf_len = strlen(token);
		if (rule_buf_len >= MIN_LEN_SINGLE_RULE && rule_buf_len <= MAX_LEN_SINGLE_RULE) {
			memset(rule_buf, 0x0, sizeof(rule_buf));
			memcpy(rule_buf, token, rule_buf_len);

			if (kstrtoull(rule_buf, 0, &rule) == 0) {
				m.status = rule;
				mce_add_rule(r, &m);
			} else {
				pr_err("%s invalid rule value: %s\n", r->name, token);
				continue;
			}
		} else {
			pr_err("%s invalid rule length: %s\n", r->name, token);
			continue;
		}
	}
}

static void mce_parse_substr(char *sub_str)
{
	char *delim_pos, *options;
	int name_len, i;

	delim_pos = strchr(sub_str, ':');
	if (delim_pos == NULL) {
		pr_err("not found ':' character in '%s'\n", sub_str);
		return;
	}

	options = delim_pos + 1;
	name_len = delim_pos - sub_str;
	for (i = 0; i < MAX_NUM_OF_RULES_TYPE; i++) {
		if (strncmp(sub_str, rules[i].name, name_len) == 0)
			mce_parse_rules(options, i);
	}
}

/*
 * configure parameter in cmdline.
 * usage:   mce_rules=type1:rule1,rule2,rule3#type2:rule1,rule2,rule3#type3:rule1,rule2,rule3
 * example: mce_rules=not_panic:0x80#panic_notify:0x0,0x80#mem_isolate:0xc00000000100080
 */
static int __init mce_rules_setup(char *cmdline)
{
	char * const delim = "#";
	char *sub_str;

	if (cmdline == NULL) {
		pr_err("mce_rules is empty\n");
		return 1;
	}

	while (NULL != (sub_str = strsep(&cmdline, delim)))
		mce_parse_substr(sub_str);

	mce_show_rules();
	return 0;
}
early_param("mce_rules", mce_rules_setup);

void do_notify_mce_panic(void)
{
	if (mce_check_mem_err() > 0)
		notify_mce_panic();
}

void do_mce_check_not_panic(struct mce *m, int *severity)
{
	if (mce_check_not_panic(m))
		*severity = MCE_SOME_SEVERITY;
}
